package rlex.classes;

import java.util.ArrayList;
import rlex.classes.program.*;
import java.util.List;

import org.w3c.dom.ProcessingInstruction;

public class Semantic {
	private Program program;
	private IdentifierStack idents;
	
	public Semantic(Program _program) {
		program = _program;
		idents = new IdentifierStack();
	}

	public void run() throws Exception {
		System.out.print("Parsing : " + program.getName() + "\n");
		registerVars(program.getVars());
		idents.display(); // DISPLAY
		registerProcedures(program.getProcedures());
		checkCommand(program.getCommand());
	}
	
	private void registerProcedures(ProcedureList procedures) throws Exception {
		List<Procedure> list = procedures.getProcedures();
		for (int i = 0; i < list.size(); i++) {
			registerProcedure(list.get(i));
		}
	}
	
	private void registerProcedure(Procedure procedure) throws Exception {
		Identifier id = new Identifier();
		id.setName(procedure.getName());
		id.setType(Token.PROCEDURE);
		id.setAttriblist(generateArrayListIdentifier(procedure.getParameters())); // Register parameters
		idents.AddIdentifier(id); // Registering Procedure
		idents.PushNewLevel(); // Entering in procedure (Local Ids);
		registerVars(procedure.getParameters());
		registerVars(procedure.getVars());
		idents.display(); // DISPLAY
		registerProcedures(procedure.getSubprocedure());
		checkCommand(procedure.getCode());
		idents.PopLevel();
	}
	
	private void registerVars(VariableDeclarationList vars) throws Exception {
		ArrayList<Identifier> arr = generateArrayListIdentifier(vars);
		for (int i = 0; i < arr.size(); i++) {
			idents.AddIdentifier(arr.get(i));
		}
	}
	
	private ArrayList<Identifier> generateArrayListIdentifier(VariableDeclarationList vars) {
		ArrayList<Identifier> arr = new ArrayList<Identifier>();
		if (vars != null) {
			List<VariableDeclaration> varlist = vars.getVars();
			if (varlist != null) {
				for (int i = 0; i < varlist.size(); i++) {
					Identifier id = new Identifier();
					id.setName(varlist.get(i).getName());
					id.setType(varlist.get(i).getType());
					arr.add(id);
				}
			}
		}
		return arr;
	}
	
	private boolean checkCommandList(CommandList commandlist) throws Exception {
		List<Command> command = commandlist.getCommands();
		for (int i = 0; i < command.size(); i++) {
			if (!checkCommand(command.get(i))) {
				return false;
			}
		}
		return true;
	}
	
	private boolean checkCommand(Command command) throws Exception {
		if (CommandBlock.class.equals(command.getClass())) {
			CommandBlock com = (CommandBlock)command;
			return checkCommandList(com.getCommands());
		}
		if (CommandExpression.class.equals(command.getClass())) {
			CommandExpression com = (CommandExpression)command;
			String type = getExpressionType(com.getExp());
			if (type != null) {
				return true;
			}
		}
		if (CommandIf.class.equals(command.getClass())) {
			CommandIf com = (CommandIf)command;
			String type = getExpressionType(com.getCond());
			if (type != Token.BOOLEAN) {
				throw new Exception("If condition not BOOLEAN : (" + type + ") " + com.getCond());
			}
			boolean retthen = checkCommand(com.getCommand_then());
			boolean retelse = true;
			if (com.getCommand_else() != null) {
				retelse = checkCommand(com.getCommand_else());
			}
			if (retthen && retelse) {
				return true;
			} else {
				return false;
			}
		}
		if (CommandWhile.class.equals(command.getClass())) {
			CommandWhile com = (CommandWhile)command;
			String type = getExpressionType(com.getCond());
			if (type != Token.BOOLEAN) {
				throw new Exception("While condition not BOOLEAN : (" + type + ") " + com.getCond());
			}
			return checkCommand(com.getCode());
		}
		return true;
	}
	private String getExpressionType(Expression exp) throws Exception {
		String ret = null;
		if (ExpressionNumber.class.equals(exp.getClass())) {
			// Return Number type
			ret = ((ExpressionNumber)exp).getType();
		}
		if (ExpressionAffect.class.equals(exp.getClass())) {
			// Return the expression type
			ExpressionAffect expaffec = (ExpressionAffect)exp;
			Identifier ident = idents.getIdentifier(expaffec.getIdentifier());
			if (ident == null) {
				throw new Exception("Unknow identifier " + expaffec.getIdentifier());
			}
			String typeexp = getExpressionType(expaffec.getExp());
			if (typeexp.equals(ident.getType()) ||
					((typeexp.equals(Token.REAL) || typeexp.equals(Token.INTEGER)) &&
							(ident.getType().equals(Token.REAL) || ident.getType().equals(Token.INTEGER)))) {
				if (ident.getType().equals(Token.INTEGER) && typeexp.equals(Token.REAL)) {
					throw new Exception (exp + " , integer := real , Incorrect assignation\n");
				}
				ret = typeexp;
			} else {
				throw new Exception("Bad type : " + typeexp + " != " + ident.getType() + " : "  + expaffec);
			}
		}
		if (ExpressionOperation.class.equals(exp.getClass())) {
			ExpressionOperation expop = (ExpressionOperation)exp;
			String typeleft = getExpressionType(expop.getLeft());
			String typeright = getExpressionType(expop.getRight());
			
			String op = expop.getOperator();
			if (op.equals("and") || op.equals("or")) {
				if (typeleft.equals(Token.BOOLEAN) && typeright.equals(Token.BOOLEAN)) {
					ret = Token.BOOLEAN;
				} else {
					throw new Exception("Bad type for this expression (boolean) : " + exp);
				}
			} else if (op.equals("+") || op.equals("-") || op.equals("*")) {
				// Check the left and right type
				if (typeleft.equals(Token.REAL)) {
					if (typeright.equals(Token.INTEGER) || typeright.equals(Token.REAL)) {
						ret = Token.REAL;
					}
					if (typeright.equals(Token.BOOLEAN)) {
						throw new Exception("Real vs Boolean error : " + exp);
					}
				} else if (typeleft.equals(Token.INTEGER)) {
					if (typeright.equals(Token.INTEGER)) {
						ret = Token.INTEGER;
					} else if (typeright.equals(Token.REAL)) {
						ret = Token.REAL;
					} else if (typeright.equals(Token.BOOLEAN)) {
						throw new Exception("Integer vs Boolean error : " + exp);
					}
				} else {
					throw new Exception("Bad type error : " + exp);
				}
			} else if (op.equals("/")) {
				if ((typeleft.equals(Token.REAL) || typeleft.equals(Token.INTEGER)) &&
						(typeright.equals(Token.REAL) || typeright.equals(Token.INTEGER))) {
					return Token.REAL;
				} else {
					throw new Exception("Bad type error : " + exp);
				}
			} else {
				throw new Exception("Unknow Operation : " + exp);
			}
		}
		if (ExpressionRelational.class.equals(exp.getClass())) {
			// Just check type on left and right
			getExpressionType(((ExpressionRelational)exp).getLeft());
			getExpressionType(((ExpressionRelational)exp).getRight());
			ret = Token.BOOLEAN;
		}
		if (ExpressionIdentifier.class.equals(exp.getClass())) {
			String name = ((ExpressionIdentifier)exp).getName();
			Identifier id = idents.getIdentifier(name);
			if (id == null) {
				throw new Exception("Unknow identifier " + name);
			}
			if (id.getType().equals(Token.BOOLEAN)
						|| id.getType().equals(Token.REAL)
						|| id.getType().equals(Token.INTEGER)) {	
				ret = id.getType();
			} else {
				throw new Exception(name + " is not a variable : " + id.getType());
			}
		}
		if (ExpressionFunctionCall.class.equals(exp.getClass())) {
			ExpressionFunctionCall func = (ExpressionFunctionCall)exp;
			Identifier id = idents.getIdentifier(func.getProcedureName());
			if (id == null) {
				throw new Exception("Unknow function name " + func.getProcedureName());
			}
			if (id.getType().equals(Token.PROCEDURE)) {
				// Check parameters
				List<Expression> expList = func.getAttribs().getExpressions();
				if (expList.size() != id.getAttriblist().size()) {
					throw new Exception("Bad Nb Parameters : " + func.toString());
				}
				for (int i = 0; i < expList.size(); i++) {
					String type = getExpressionType(expList.get(i));
					if (!type.equals(id.getAttriblist().get(i).getType())) {
						throw new Exception("Bad type for parameter " + (i + 1) + " : " + id.getAttriblist().get(i).getName() + " for " + func);
					}
				}
				ret = Token.NONE;
			} else {
				throw new Exception(func.getProcedureName() + " is not a function : " + id.getType());
			}
		}
		// Unexpected error
		System.out.print("getExpressionType(" + exp + ") => " + ret + "\n");
		return ret;
	}
}
